Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ivy): instantiate dirs in correct order #23178

Closed
wants to merge 2 commits into from
Closed

Conversation

kara
Copy link
Contributor

@kara kara commented Apr 5, 2018

Currently, directives have to be listed in directive defs in the correct order or injected directives will not be found. For example, consider the following setup, where DirA injects DirB, but DirA is created first.

<div dirA dirB></div>
class DirA {
  constructor(dirB: DirB) {...}
}

class DirB {...}

In this case,DirB won't be found by DI because it hasn't been created yet.

This PR allows DI to eagerly instantiate a directive dependency if we know it's been defined on the same node, but not yet created. The extra step will only occur on first template pass because directives are now saved to tView.directives in the correct instantiation order.

It also adds logic to throw a helpful error if there's a circular dependency.

Notes:

  • currentMatches has been added to the TView as a temporary holder for selector matches on the node while injection occurs. This allows DI to check whether a directive is queued to be created on the same node and if so, use the def provided to instantiate the injected directive first.
    • Unfortunately, we can't cut out the middle man and check directly if the current node would match the selector in directiveInject because it would require an expensive megamorphic property access to get the selector (token.ngDirectiveDef.selector).
    • Another option might be to pass the matches into the factory fn directly, but this would muddy up the API and add extra bytes to the generated code.

@kara kara added the state: WIP label Apr 5, 2018
@kara kara added target: major This PR is targeted for the next major release comp: ivy labels Apr 5, 2018
@kara kara force-pushed the order branch 3 times, most recently from 40bd9f8 to 7b1ec24 Compare April 5, 2018 16:56
@kara kara added action: review The PR is still awaiting reviews from at least one requested reviewer and removed state: WIP labels Apr 5, 2018
@kara kara requested a review from mhevery April 5, 2018 16:58
// If we *didn't* find the directive for the token and we are searching the current node's
// injector, it's possible the directive is on this node and hasn't been created yet.
let instance: T|null;
if (injector === di && (instance = searchMatchesQueuedForCreation<T>(node, token))) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this information get cached so that subsequent instantiations are fast? In other words, can we rearrange the order of directives until it is the correct order?

It looks to me that every time we want to inject parent directive we will go to searchMatchesQueuedForCreation which will have negative perf impact. I think the logic should be something like

if (first instantiation) {
  record the order in which the directives have been created (lookahead if needed)
  update the current list of directives to the recorded list.
} else {
  directives are in correct order just instantiate them.
}

Copy link
Contributor Author

@kara kara Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I think you mentioned in a later comment, this is the way it already works. The directives are saved in the correct order after the first template pass, so subsequent runs will just instantiate the directives in the right order.

It's worth noting that if you are injecting a parent directive, this search flow will never be entered (even in the first template pass) because DI would have retrieved the parent bloom injector and the injector === di check would fail.

The only time the search occurs is:
- on first template pass
- if token's bit has been added to the bottom level bloom filter during directive matching (so we know it's in this list)
- AND the directive was not already created (out of order)

* If we visit a directive that has a value set to CIRCULAR, we know we've
* already seen it, and thus have a circular dependency.
*/
export const CIRCULAR = '__CIRCULAR__';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it is a string someone could create it. Better to do {mark: 'circular'} This way no one can got hold of that instance.

Copy link
Contributor Author

@kara kara Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The matches array only contains directive defs, directive indices, and this constant, so there's no way user-provided values could be in there. I ended up using a string to avoid unnecessary object creation. Happy to change if you still think it's worth it.

node.tNode = tData[index] = tNode;
node.tNode = tData[index] = createTNode(name, attrs || null, containerData);
cacheMatchingDirectivesForNode(node.tNode, currentView.tView, localRefs || null);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh I see, I think this is where the caching happens. So my previous comment may not be applicable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, directives are saved in the correct order for instantiation after the first run.

@buildsize
Copy link

buildsize bot commented Apr 5, 2018

File name Previous Size New Size Change
bundle.min.js 52.21 KB 52.64 KB 438 bytes (1%)
bundle.min.js.brotli 14.06 KB 14.14 KB 79 bytes (1%)

@mary-poppins
Copy link

You can preview 235612e at https://pr23178-235612e.ngbuilds.io/.

@angular angular deleted a comment from mary-poppins Apr 5, 2018
@kara kara added type: bug/fix and removed action: review The PR is still awaiting reviews from at least one requested reviewer labels Apr 5, 2018
/** <div dirA dirB dirC></div> */
const App = createComponent('app', function(ctx: any, cm: boolean) {
if (cm) {
elementStart(0, 'div', ['dirA', '', 'dirB', '', 'dirC', 'dirC']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'dirC', 'dirC' -> 'dirC', ''?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! It shouldn't affect the test, but it doesn't match the comment, so I've updated the PR.

@mary-poppins
Copy link

You can preview e855190 at https://pr23178-e855190.ngbuilds.io/.

@kara
Copy link
Contributor Author

kara commented Apr 5, 2018

presubmit

@kara kara added the action: merge The PR is ready for merge by the caretaker label Apr 5, 2018
@IgorMinar IgorMinar closed this in 628303d Apr 5, 2018
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes target: major This PR is targeted for the next major release type: bug/fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants